package com.why.reflect;

import com.why.annotation.ApiClass;
import com.why.annotation.ApiMethod;
import com.why.annotation.ParamClass;
import com.why.annotation.ParamField;
import com.why.dto.Api;
import com.why.util.StringUtil;
import org.codehaus.plexus.util.StringUtils;

import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

public class AnnotationParse {
    // 类加载器
    private URLClassLoader loader;

    public AnnotationParse(URL[] urls) {
        this.loader = new URLClassLoader(urls);
    }

    /**
     * 根据jar路径，包名，className，method,以及方法中对应的参数className
     * 如果指定jar路径和apiPage，则会找对应加载对应包下的所有类
     *
     * @param jarPath           jar的绝对路径
     * @param apiPackage        api包名
     * @param apiClassName      api类名
     * @param apiMethodName     api方法名
     * @param apiParamClassName 方法中参数类名
     */
    public List<Api> generateApisObj(String jarPath, String apiPackage, String apiClassName, String apiMethodName, String apiParamClassName) throws Exception {
        List<Api> apis = new ArrayList<>();
        if (StringUtil.isEmpty(apiPackage)) {
            apis.add(generateApiObj(apiClassName, apiMethodName, apiParamClassName));
            return apis;
        }
        if (StringUtil.isEmpty(jarPath)) {
            throw new IllegalArgumentException("jarPath 参数配置为空！");
        }
        JarFile jar = new JarFile(jarPath);
        Enumeration<JarEntry> enumFiles = jar.entries();
        JarEntry entry;
        // 1. 遍历jar中的类
        while (enumFiles.hasMoreElements()) {
            entry = enumFiles.nextElement();
            String classFullName = entry.getName();
            String pageClazzName = classFullName.replace("/", ".");
            // 包含全路径，若apiClassName不为空，进行apiClassName的匹配
            if (pageClazzName.contains(apiPackage) && (apiClassName == null || pageClazzName.equals(apiClassName + ".class"))) {
                if (!classFullName.endsWith(".class")) {
                    continue;
                }
                Class<?> clazz = loader.loadClass(pageClazzName.substring(0, classFullName.lastIndexOf(".")));
                Class apiClazz = loader.loadClass(ApiClass.class.getName());
                if (clazz.isAnnotationPresent(apiClazz)) {
                    // 加载类中方法
                    for (Method method : clazz.getDeclaredMethods()) {
                        Api api = new Api();
                        handleApiClazz(api, clazz);
                        // 2. 进行apiMethodName的匹配
                        if (apiMethodName != null && !method.getName().equals(apiMethodName)) {
                            continue;
                        }
                        handlerApiMethod(api, method);
                        // 3. 获取方法中参数的配置信息
                        for (Class<?> methodParam : method.getParameterTypes()) {
                            if (apiParamClassName != null && !methodParam.getName().equals(apiParamClassName)) {
                                continue;
                            }
                            List<Api.Param> requestParamList = handlerApiParam(methodParam.getName());
                            if (requestParamList.isEmpty()) {
                                continue;
                            }
                            Api.Request request = new Api.Request();
                            request.setRequestClassName(methodParam.getSimpleName());
                            request.setRequestParams(requestParamList);
                            api.addRequest(request);
                        }
                        if (!"void".equals(method.getReturnType().getName())) {
                            List<Api.Param> responseParamList = handlerApiParam(method.getReturnType().getName());
                            api.setResponse(responseParamList);
                        }
                        apis.add(api);
                    }
                }
            }
        }
        return apis;
    }

    /**
     * 生成Api对象
     *
     * @return Api
     * @throws Exception
     */
    public Api generateApiObj(String apiClassName, String apiMethodName, String apiParamClassName) throws Exception {
        /*
         * 这里有个大坑：clazz.isAnnotationPresent(ApiClass.class)永远返回false，clazz.getAnnotation(ApiClass.class)永远返回null。
         * 具体参考：https://blog.csdn.net/zhangyufei1107/article/details/79760475
         * 大概是不同的类加载器导致的吧！
         * 所以需要使用加载项目和仓库的类加载器加载自定义注解得到Class，才是项目里面使用的注解，
         * 得到注解后也不能强转为自定义注解，需要使用反射调用获取属性值。
         */

        Class clazz = loader.loadClass(apiClassName);
        Api api = new Api();

        // 处理API类上面的ApiClass注解（可空）
        handleApiClazz(api, clazz);

        // 获取API方法
        Method[] methods = clazz.getMethods();
        Optional<Method> any = Arrays.stream(methods).filter(method -> Objects.equals(method.getName(), apiMethodName)).findAny();
        if (!any.isPresent()) {
            throw new IllegalArgumentException("API方法" + apiMethodName + "不存在！");
        }
        Method method = any.get();

        // 处理API方法上面的ApiMethod注解，不能为空
        handlerApiMethod(api, method);

        // 处理ApiParamClass
        handleApiParamClass(api, apiParamClassName);

        return api;
    }

    /**
     * 从class中获得对应的参数信息
     */
    private void handleApiClazz(Api api, Class clazz) throws Exception {
        Class apiClazz = loader.loadClass(ApiClass.class.getName());
        if (clazz.isAnnotationPresent(apiClazz)) {
            Annotation annotation = clazz.getAnnotation(apiClazz);
            String description = (String) apiClazz.getMethod("description").invoke(annotation),
                    basePath = (String) apiClazz.getMethod("basePath").invoke(annotation);
            api.setTitle(description);
            api.setUrl(basePath);
        }
    }

    /**
     * 从method中获得对应的参数信息
     */
    private void handlerApiMethod(Api api, Method method) throws Exception {
        Class apiMetnod = loader.loadClass(ApiMethod.class.getName());
        if (method.isAnnotationPresent(apiMetnod)) {
            Annotation annotation = method.getAnnotation(apiMetnod);
            String url = (String) apiMetnod.getMethod("url").invoke(annotation),
                    requestMethod = (String) apiMetnod.getMethod("requestMethod").invoke(annotation),
                    description = (String) apiMetnod.getMethod("description").invoke(annotation);
            api.setUrl(api.getUrl() + url);
            api.setMethod(requestMethod);
            api.setExplain(description);
        } else {
            throw new IllegalArgumentException("API方法" + method.getName() + "缺少ApiMethod注解！");
        }
    }

    /**
     * 获取apiParamClassName类参数信息
     * 如果该类没有ParamClass注解，则返回空list
     */
    private List<Api.Param> handlerApiParam(String apiParamClassName) throws Exception {
        Class clazz = loader.loadClass(apiParamClassName);
        // 判断是否有ParamClass注解
        Class paramClazz = loader.loadClass(ParamClass.class.getName());
        List<Api.Param> requestParam = new ArrayList<>();
        // 没有ParamClass注解，直接返回
        if (!clazz.isAnnotationPresent(paramClazz)) {
            return requestParam;
        }

        AtomicInteger i = new AtomicInteger(1);

        for (Field field : clazz.getDeclaredFields()) {
            Api.Param param = prepareParam(field, i);
            requestParam.add(param);
        }
        return requestParam;
    }


    /**
     * 从Param中获得对应的参数信息
     */
    private void handleApiParamClass(Api api, String apiParamClassName) throws Exception {
        Class clazz = loader.loadClass(apiParamClassName);
        // 判断是否有ParamClass注解
        Class paramClazz = loader.loadClass(ParamClass.class.getName());
        boolean exist = clazz.isAnnotationPresent(paramClazz);

        Field[] fields = clazz.getDeclaredFields();
        AtomicInteger i = new AtomicInteger(1);
        List<Api.Param> params = new ArrayList<>();
        for (Field field : fields) {
            // 类存在ParamClass注解，所有private字段都要参与生成文档，但是只有名称和类型
            if (exist) {
                if (field.getModifiers() == Modifier.PRIVATE) {
                    Api.Param param = new Api.Param();
                    param.setNo(i.getAndIncrement());
                    param.setName(field.getName());
                    param.setType(field.getType().getSimpleName());
                    // 空的字段留白
                    param.setDescription("");
                    param.setLength("");
                    param.setNullAble("");
                    param.setRemark("");

                    params.add(param);
                }
                continue;
            }

            // 类没有ParamClass注解，只处理有ParamField注解的字段
            Class paramField = loader.loadClass(ParamField.class.getName());
            if (field.isAnnotationPresent(paramField)) {
                Api.Param param = new Api.Param();
                // 自增序号
                param.setNo(i.getAndIncrement());
                Annotation annotation = field.getAnnotation(paramField);
                // 字段名
                String name = (String) paramField.getMethod("name").invoke(annotation);
                param.setName(StringUtils.isEmpty(name) ? field.getName() : name);
                // 说明
                String description = (String) paramField.getMethod("description").invoke(annotation);
                param.setDescription(description);
                // 类型
                String type = (String) paramField.getMethod("type").invoke(annotation);
                param.setType(StringUtils.isEmpty(type) ? field.getType().getSimpleName() : type);
                // 长度，为零就留空
                int length = (int) paramField.getMethod("length").invoke(annotation);
                param.setLength(length == 0 ? "" : String.valueOf(length));
                // 是否可为空
                boolean nullAble = (boolean) paramField.getMethod("nullAble").invoke(annotation);
                param.setNullAble(nullAble ? "Y" : "N");
                // 备注
                String remark = (String) paramField.getMethod("remark").invoke(annotation);
                param.setRemark(remark);

                params.add(param);
            }
        }
        api.setParams(params);
    }

    /**
     * 获取对应字段的信息
     * 如果字段用ParamField注解，先获取注解中的信息
     */
    private Api.Param prepareParam(Field field, AtomicInteger i) throws Exception {
        Api.Param param = new Api.Param();
        Class paramField = loader.loadClass(ParamField.class.getName());
        if (field.isAnnotationPresent(paramField)) {
            // 自增序号
            param.setNo(i.getAndIncrement());
            Annotation annotation = field.getAnnotation(paramField);
            // 字段名
            String name = (String) paramField.getMethod("name").invoke(annotation);
            param.setName(StringUtils.isEmpty(name) ? field.getName() : name);
            // 说明
            String description = (String) paramField.getMethod("description").invoke(annotation);
            param.setDescription(description);
            // 类型
            String type = (String) paramField.getMethod("type").invoke(annotation);
            param.setType(StringUtils.isEmpty(type) ? field.getType().getSimpleName() : type);
            // 长度，为零就留空
            int length = (int) paramField.getMethod("length").invoke(annotation);
            param.setLength(length == 0 ? "" : String.valueOf(length));
            // 是否可为空
            boolean nullAble = (boolean) paramField.getMethod("nullAble").invoke(annotation);
            param.setNullAble(nullAble ? "Y" : "N");
            // 备注
            String remark = (String) paramField.getMethod("remark").invoke(annotation);
            param.setRemark(remark);
        } else {
            if (field.getModifiers() == Modifier.PRIVATE) {
                param.setNo(i.getAndIncrement());
                param.setName(field.getName());
                param.setType(field.getType().getSimpleName());
                // 空的字段留白
                param.setDescription("");
                param.setLength("");
                param.setNullAble("");
                param.setRemark("");
            }
        }
        return param;
    }
}
